/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.binding;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import edu.ucsd.db.bassarisk.schema.AttributeClass;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.schema.RunClass;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public class Bindings {
  public static class AggregatedValueBinding extends ComputedValueBinding {
    private final Binding aggregationContext;
    private Binding inputSequence;
    private final EdgeFunction f;

    AggregatedValueBinding(final int id, final Binding aggregationContext,
                           EdgeFunction f) {
      super(id);
      this.aggregationContext = aggregationContext;
      this.f = f;
    }

    public Binding getInputSequence() {
      return this.inputSequence;
    }

    public void setInputSequence(final Binding aggreatedData) {
      this.inputSequence = aggreatedData;
    }

    @Override
    public String toString() {
      return String.format("B%s:%s( %s:%s at %s )", this.getId(), "Aggregate",
                           this.f, this.inputSequence.getId(),
                           this.aggregationContext.getId());
    }

    public EdgeFunction getFuntion() {
      return f;
    }
  }

  public static abstract class Binding {
    private final int id;
    private BindingCombination parent;

    private Binding(final int id) {
      this.id = id;
      this.parent = null;
    }

    public abstract DataSequenceClass boundSequenceClass();

    public int getId() {
      return this.id;
    }

    public BindingCombination getParent() {
      return this.parent;
    }

    public boolean inProduct() {
      return this.parent == null ? false : this.parent instanceof BindingProduct;
    }

    public boolean inUnion() {
      return this.parent == null ? false : this.parent instanceof BindingUnion;
    }

    public void setParent(final BindingCombination parent) {
      this.parent = parent;
    }
  }

  public static abstract class BindingCombination extends Binding {
    private final List<Binding> combined;
    private final DataSequenceClass runClass;

    private BindingCombination(final int id, final DataSequenceClass runClass,
                               final List<Binding> subExprBindingsForKey) {
      super(id);
      this.runClass = runClass;
      this.combined = new ArrayList<Binding>();
      this.combined.addAll(subExprBindingsForKey);
    }

    @Override
    public DataSequenceClass boundSequenceClass() {
      return this.runClass;
    }

    public Iterable<Binding> getCombinedBindings() {
      return this.combined;
    }

    protected abstract String getName();

    @Override
    public String toString() {
      List<Integer> combinedIds = new ArrayList<Integer>();
      for (Binding binding : this.combined) {
        combinedIds.add(binding.getId());
      }
      return String.format("B%s:%s( %s )", this.getId(), this.getName(),
                           combinedIds);
    }
  }

  public static class BindingProduct extends BindingCombination {
    BindingProduct(final int id, final DataSequenceClass runClass,
                   final List<Binding> subExprBindingsForKey) {
      super(id, runClass, subExprBindingsForKey);
    }

    @Override
    protected String getName() {
      return "Product";
    }
  }

  public static class BindingUnion extends BindingCombination {
    BindingUnion(final int id, final DataSequenceClass runClass,
                 final List<Binding> subExprBindingsForKey) {
      super(id, runClass, subExprBindingsForKey);
    }

    @Override
    protected String getName() {
      return "Union";
    }
  }

  public abstract static class ComputedValueBinding extends Binding {
    ComputedValueBinding(final int id) {
      super(id);
    }

    @Override
    public DataSequenceClass boundSequenceClass() {
      return DataSequenceClass.COMPUTED;
    }
  }

  public static class MapFunctionValueBinding extends ComputedValueBinding {
    private final Collection<Binding> inputs;
    private final String name;

    MapFunctionValueBinding(final int id, final String name,
                            final Collection<? extends Binding> inputs) {
      super(id);
      this.name = name;
      this.inputs = new ArrayList<Binding>();
      this.inputs.addAll(inputs);
    }

    @Override
    public String toString() {
      List<Integer> inputIds = new ArrayList<Integer>();
      for (Binding binding : this.inputs) {
        inputIds.add(binding.getId());
      }
      return String.format("B%s:Fu( %s, %s )", this.getId(), this.name, inputIds.toString());
    }
  }

  public static class PositionAttributeBinding extends Binding {
    private final PositionBinding attachedPositions;
    private final AttributeClass attributeClass;

    PositionAttributeBinding(final int id, final AttributeClass attributeClass,
                             final PositionBinding attachedPositions) {
      super(id);
      this.attributeClass = attributeClass;
      this.attachedPositions = attachedPositions;
    }

    @Override
    public DataSequenceClass boundSequenceClass() {
      return this.attributeClass;
    }

    @Override
    public String toString() {
      return String.format("B%s:%s( %s, %s )", this.getId(), "PositionAttrSet",
                           this.attributeClass.getName(), attachedPositions.getId());
    }
  }

  public static class PositionBinding extends Binding {
    private final String referenceName;
    private String valueFilter;

    PositionBinding(final int id, final String valueFilter, final String name) {
      super(id);
      this.valueFilter = valueFilter;
      this.referenceName = name;
    }

    @Override
    public DataSequenceClass boundSequenceClass() {
      return RunClass.POSITION_SPACE;
    }

    public String getReferenceName() {
      return this.referenceName;
    }

    public String getValueFilter() {
      return this.valueFilter;
    }

    public void setFilter(final String filter) {
      this.valueFilter = filter;
    }

    @Override
    public String toString() {
      return String.format("B%s:%s( %s )", this.getId(), "PositionSet",
                           this.valueFilter);
    }
  }

  public static class RunAttributeBinding extends Binding {
    private final RunBinding attachedRuns;
    private final AttributeClass attributeClass;

    RunAttributeBinding(final int id, final AttributeClass attributeClass,
                        final RunBinding attachedRuns) {
      super(id);
      this.attributeClass = attributeClass;
      this.attachedRuns = attachedRuns;
    }

    @Override
    public DataSequenceClass boundSequenceClass() {
      return this.attributeClass;
    }

    @Override
    public String toString() {
      return String.format("B%s:%s( %s, %s )", this.getId(), "RunAttrSet",
                           this.attributeClass.getName(), attachedRuns.getId());
    }
  }

  public static class RunBinding extends Binding {
    private final DataSequenceClass runClass;

    RunBinding(final int id, final DataSequenceClass runClass) {
      super(id);
      this.runClass = runClass;
    }

    @Override
    public DataSequenceClass boundSequenceClass() {
      return this.runClass;
    }

    @Override
    public String toString() {
      return String.format("B%s:%s( %s )", this.getId(), "RunSet", this.runClass.getName());
    }
  }
}